//
//  Cipher.swift
//  Utilities
//
//  Created by x on 2024/8/18.
//

import CryptoSwift

public protocol Encryption {
    func encrypt(_ plaintext: String) throws -> String
    func decrypt(_ ciphertext: String) throws -> String
}

public protocol Hashing {
    func hash(_ data: String) throws -> String
}

public protocol HashMac {
    func hashMac(message: String, key: String) throws -> String
}

public class EncryptionFactory {
    public static func createAesEncryption(_ key: String, _ iv: String) -> Encryption {
        return AesEncryption(key: key, iv: iv)
    }
    public static func createBase64Encryption() -> Encryption {
        return Base64Encryption()
    }
}

public class HashingFactory {
    public static func createMD5Hashing() -> Hashing {
        return MD5Hashing()
      }

      public static func createSHA256Hashing() -> Hashing {
        return SHA256Hashing()
      }

      public static func createSHA512Hashing() -> Hashing {
        return SHA512Hashing()
      }
}

public class HashMacFactory {
  public static func createHashMac() -> HashMac {
    return HmacSHA1()
  }
}

// MARK: private ---
public class AesEncryption: Encryption {
    
    private let key: String
    private let iv: String
    
    public init(key: String, iv: String) {
        self.key = key
        self.iv = iv
    }
    
    public func encrypt(_ plaintext: String) throws -> String {
        
        let aes = try AES(key: key, iv: iv, padding: .pkcs7)
        let ciphertext = try aes.encrypt(Array(plaintext.utf8))
        return String(bytes: ciphertext, encoding: .utf8) ?? ""
    }
    
    public func decrypt(_ ciphertext: String) throws -> String {
        let dec = try AES(key: key, iv: iv, padding: .pkcs7)
        let decrypted = try dec.decrypt(Array(ciphertext.utf8))
        return String(bytes: decrypted, encoding: .utf8) ?? ""
    }
}

public class Base64Encryption: Encryption {
    public func encrypt(_ plaintext: String) throws -> String {
        return plaintext.data(using: .utf8)?.base64EncodedString() ?? ""
    }
    
    public func decrypt(_ ciphertext: String) throws -> String {
        if let data = Data(base64Encoded: ciphertext), let decodedString = String(data: data, encoding: .utf8) {
            return decodedString
        }
        return ""
    }
}

public class MD5Hashing: Hashing {
    public func hash(_ data: String) throws -> String {
        return data.md5()
    }
}

public class SHA256Hashing: Hashing {
    public func hash(_ data: String) throws -> String {
        return data.sha256()
    }
}

public class SHA512Hashing: Hashing {
    public func hash(_ data: String) throws -> String {
        return data.sha512()
    }
}

//public class HmacSHA1: HashMac {
//    public func hashMac(message: String, key: String) throws -> String {
//        let strSequence = try HMAC(key: key, variant: .sha1).authenticate(Array(message.utf8))
//        guard let str = String(bytes: strSequence, encoding: .utf8) else { return "" }
//        return str
//    }
//}
public class HmacSHA1: HashMac {
    public func hashMac(message: String, key: String) throws -> String {
        let hmac = try HMAC(key: key, variant: .sha1).authenticate(Array(message.utf8))
        return hmac.map { String(format: "%02hhx", $0) }.joined()
    }
}
